/*
  File: KATS.C
*/

#include <ats.h>
#include <p_gen.h>
#include <p_file.h>
#include <wlib.h>
#include <olib.g>
#include <appman.g>
#include <gate.g>
#include <kats.rsg>

#define KATS_MODS       (W_CTRL_MODIFIER|W_PSION_MODIFIER)
#define KATS_RECORD     0x20
#define KATS_PLAYBACK   W_KEY_RETURN
#define KATS_PLAYBACK2  W_KEY_TAB
#define KATS_SAVE       W_KEY_DIAMOND

#define MAX_NUM_KEYS    128         /* maximum number of keys per macro */
#define MACRO_NAME_LEN   24         /* includes zero terminator */

typedef struct
    {
    TEXT name[MACRO_NAME_LEN];
    WORD num_keys;
    ATS_KEY_DEF keys[MAX_NUM_KEYS];
    } KATS_MACRO;

GLREF_D VOID *DatCommandPtr;
GLREF_D VOID *DatGate;

LOCAL_D HANDLE olib;

LOCAL_D VOID *vaxvar;
LOCAL_D UWORD nmacros;

LOCAL_D KATS_MACRO mac_rec;         /* macro being recorded */
LOCAL_D ATS_KEY full_key;           /* key being recorded */

LOCAL_D KATS_MACRO *mac_play;       /* macro being played back */
LOCAL_D INT play_keys;

LOCAL_D INT changed;

LOCAL_D INT pid;

LOCAL_D INT record;
LOCAL_D INT playback;

LOCAL_D WS_EV event;
LOCAL_D WORD rec_stat;
LOCAL_D WORD rec_active;
LOCAL_D WORD play_stat;
LOCAL_D WORD play_active;

LOCAL_D TEXT *pmac_name;
LOCAL_D ATS_DIAL_DEF dl_edit;
LOCAL_D ATS_DIAL_DEF dl_choose;
LOCAL_D ATS_DIAL_DEF dl_confirm;
LOCAL_D ATS_DIAL_DEF dl_save;

LOCAL_C INT GetForegroundClient(VOID)
    {
    UWORD pids[WS_MAX_CLIENTS+1];

    wGetProcessList(&pids[0]);
    return(pids[0]);
    }

LOCAL_C INT GetTestForegroundClient(VOID)
    {
    INT pid;

    pid=GetForegroundClient();
    if (!p_send3(DatGate,O_GT_CHECK_ATS_ON,pid))
        return(pid);        /* okay */
    p_sound(5,320);
    p_sound(5,280);
    p_sound(5,240);
    return(NULL);
    }

LOCAL_C VOID ForegroundAtsWait(INT type,ATS_MESS_BODY *pu)
    {
    INT pid;

    pid=GetTestForegroundClient();
    if (pid)
        p_msendreceivew(pid,type,pu);
    }

LOCAL_C VOID Message(TEXT *msg)
    {
    ATS_MESS_BODY u;

    u.offs=msg;
    ForegroundAtsWait(TY_ATS_MESSAGE,&u);
    }

LOCAL_C VOID TellErr(INT err)
    {
    TEXT buf[64];

    if (err==E_GEN_FAIL)
        return;
    p_errs(&buf[0],err);
    Message(&buf[0]);
    }

LOCAL_C VOID TellOom(VOID)
    {
    TellErr(E_GEN_NOMEMORY);
    }

LOCAL_C VOID PlayNextKey(VOID)
    {
    ATS_MESS_BODY u;

    u.k=mac_play->keys[play_keys];
    play_active=TRUE;
    p_msendreceivea(pid,TY_ATS_KEY,&u,&play_stat);
    }

LOCAL_C VOID RequestNextKey(VOID)
    {
    ATS_MESS_BODY u;

    u.offs=(&full_key);
    rec_active=TRUE;
    p_msendreceivea(pid,TY_ATS_GET_KEY,&u,&rec_stat);
    }

LOCAL_C VOID TransmitRecordState(VOID)
    {
    ATS_MESS_BODY u;

    u.par=record;
    p_msendreceivew(pid,TY_ATS_RECORD,&u);
    }

LOCAL_C KATS_MACRO *FindMacro(INT i)
    {
    return((KATS_MACRO *)p_send(vaxvar,O_VA_PBUF,i));
    }

LOCAL_C VOID DeleteMacro(INT i)
    {
    p_send3(vaxvar,O_VA_DELETE,i);
    nmacros--;
    changed=TRUE;
    }

LOCAL_C INT QueryOverwrite(VOID)
    {
    ATS_MESS_BODY u;

    u.d=dl_confirm;
    return(p_msendreceivew(pid,TY_ATS_DIALOG,&u));
    }

LOCAL_C VOID SaveIfConfirmed(VOID)
    {
    INT pid;
    ATS_MESS_BODY u;

    pid=GetTestForegroundClient();
    if (!pid)
        return;
    u.d=dl_save;
    if (p_msendreceivew(pid,TY_ATS_DIALOG,&u)==W_KEY_RETURN)
        {
        Message("Should save now!");
        changed=FALSE;
        }
    }

LOCAL_C INT CheckNotMatching(VOID)
    {
    INT i;
    KATS_MACRO *pmac;
    INT key;

    for (i=0; i<nmacros; i++)
        {
        pmac=FindMacro(i);
        if (p_scmpi(&pmac->name[0],&mac_rec.name[0]))
            continue;
        key=QueryOverwrite();
        if (key==W_KEY_ESCAPE)
            return(FALSE);
        DeleteMacro(i);
        return(key==' ');
        }
    return(TRUE);
    }

LOCAL_C INT GetMacroName(VOID)
    {
    ATS_MESS_BODY u;
    INT ret;

    u.d=dl_edit;
    ret=p_msendreceivew(pid,TY_ATS_DIALOG,&u);
    if (ret<0)
        {
        TellErr(ret);
        return(0);
        }
    return(CheckNotMatching());
    }

LOCAL_C VOID StartRecording(VOID)
    {
    pid=GetTestForegroundClient();
    if (!pid)
        return;
    if (GetMacroName())
        {
        record=TRUE;
        TransmitRecordState();
        Message("Recording...");
        mac_rec.num_keys=0;
        RequestNextKey();
        }
    }

#pragma save,ENTER_CALL

LOCAL_C INT AppendMacro(VOID)
    {
    RC_VAXVAR rcv;

    rcv.buf=(UBYTE *)(&mac_rec);
    rcv.len=MACRO_NAME_LEN+sizeof(WORD)+mac_rec.num_keys*sizeof(ATS_KEY_DEF);
    p_send3(vaxvar,O_VA_APPEND,&rcv);
    nmacros++;
    return(0);
    }

#pragma restore

LOCAL_C VOID StopRecording(VOID)
    {
    record=FALSE;
    TransmitRecordState();
    if (!mac_rec.num_keys)
        {
        Message("Recording cancelled");
        return;
        }
    if (p_enter1(AppendMacro)<0)
        TellOom();
    else if (mac_rec.num_keys==MAX_NUM_KEYS)
        Message("Maximum number of keystrokes reached");
    else
        Message("Finished recording");
    }

LOCAL_C VOID BuildNameList(VOID)
    {
    UBYTE *pb;
    UBYTE *new_pb;
    INT i;
    INT size;
    INT this_size;
    KATS_MACRO *pmac;

    pb=p_alloc(1);
    if (!pb)
        return;
    size=1;
    *pb=0;
    for (i=0; i<nmacros; i++)
        {
        pmac=FindMacro(i);
        this_size=p_slen(&pmac->name[0])+2;     /* include LBC and ZTS */
        new_pb=p_realloc(pb,size+this_size);
        if (!new_pb)
            {
            p_free(pb);
            return;
            }
        pb=new_pb;
        *(pb+size)=this_size-1;             /* leading byte count */
        p_scpy(pb+size+1,&pmac->name[0]);
        size+=this_size;
        }
    *pb=i;
    dl_choose.buts=(UWORD)pb;
    dl_choose.butslen=size;
    }

LOCAL_C VOID FreeNameList(VOID)
    {
    p_free((VOID *)dl_choose.buts);
    dl_choose.buts=NULL;
    }

LOCAL_C VOID StartPlayback(VOID)
    {
    ATS_MESS_BODY u;
    INT ret;

    pid=GetTestForegroundClient();
    if (!pid)
        return;
    BuildNameList();
    if (!dl_choose.buts)
        {
        TellOom();
        return;
        }
    u.d=dl_choose;
    ret=p_msendreceivew(pid,TY_ATS_DIALOG,&u);
    FreeNameList();
    if (ret<0)
        {
        TellErr(ret);
        return;
        }
    mac_play=FindMacro(ret);
    play_keys=0;
    playback=TRUE;
    PlayNextKey();
    }

LOCAL_C VOID StopPlayback(VOID)
    {
    playback=FALSE;
    }

LOCAL_C VOID NextStageInRecord(VOID)
    {
    ATS_KEY_DEF *short_key;

    short_key=(&mac_rec.keys[mac_rec.num_keys++]);
    short_key->key=full_key.keycode;
    short_key->mod=full_key.modifiers;
    changed=TRUE;
    if (mac_rec.num_keys==MAX_NUM_KEYS)
        StopRecording();
    else
        RequestNextKey();
    }

LOCAL_C VOID NextStageInPlayback(VOID)
    {
    play_keys++;
    if (play_keys==mac_play->num_keys)
        {
        StopPlayback();
        Message("Playback complete");
        }
    else
        PlayNextKey();
    }

LOCAL_C VOID MainLoop(VOID)
    {
    FOREVER
        {
        wGetEvent(&event);
    wait:
        p_iowait();
        switch (event.type)
            {
        case E_FILE_PENDING:
            if (rec_active && rec_stat!=E_FILE_PENDING)
                {
                rec_active=FALSE;
                if (record)     /* else recording cancelled */
                    NextStageInRecord();
                }
            else if (play_active && play_stat!=E_FILE_PENDING)
                {
                play_active=FALSE;
                if (playback)   /* else playback cancelled */
                    NextStageInPlayback();
                }
            goto wait;
        case WM_FOREGROUND:
            wClientPosition(WS_LAST_CLIENT_POSITION,0);
            break;
        case WM_KEY:
            switch (event.p.key.keycode)
                {
            case KATS_SAVE:
                if (record)
                    Message("Can't save macros while recording");
                else if (playback)
                    Message("Can't save macros while playing back");
                else if (!changed)
                    Message("No macro changes to save");
                else
                    SaveIfConfirmed();
                break;
            case KATS_RECORD:
                if (record)
                    StopRecording();
                else if (playback)
                    Message("Can't record while playing back");
                else
                    StartRecording();
                break;
            case KATS_PLAYBACK:
            case KATS_PLAYBACK2:
                if (playback)
                    {
                    Message("Playback terminated");
                    StopPlayback();
                    }
                else if (record)
                    Message("Can't play back while recording");
                else if (!nmacros)
                    Message("Nothing to play back");
                else
                    StartPlayback();
                }
            }
        }
    }

LOCAL_C VOID FindOlib(VOID)
    {
    p_findlib("OLIB.DYL",&olib);
    }

LOCAL_C VOID CreateGate(VOID)
    {
    HANDLE hwim;

    p_findlib("HWIM.DYL",&hwim);
    DatGate=f_newlibh(hwim,C_GATE);
    }

LOCAL_C VOID LoadDialogData(VOID)
    {
    VOID *rcb;

    rcb=f_newlibhsend(olib,C_RSCFILE,O_RS_INIT,DatCommandPtr);
    dl_edit.mainlen=p_send4(rcb,O_RS_READ,KATS_DL_MAC_NAME,&dl_edit.main);
    dl_choose.mainlen=p_send4(rcb,O_RS_READ,KATS_DL_PLAYBACK,&dl_choose.main);
    dl_confirm.mainlen=p_send4(rcb,O_RS_READ,
                                        KATS_DL_CONFIRM,&dl_confirm.main);
    dl_confirm.butslen=p_send4(rcb,O_RS_READ,
                                        KATS_AC_CONFIRM,&dl_confirm.buts);
    dl_save.mainlen=p_send4(rcb,O_RS_READ,KATS_DL_SAVE,&dl_save.main);
    dl_save.butslen=p_send4(rcb,O_RS_READ,KATS_AC_SAVE,&dl_save.buts);
    p_send2(rcb,O_DESTROY);
    dl_edit.butslen=(-2);
    dl_edit.buts=(UWORD)(&pmac_name);
    pmac_name=(&mac_rec.name[0]);
    }

LOCAL_C VOID CreateMacroStorage(VOID)
    {
    vaxvar=f_newlibhsend(olib,C_VAXVAR,O_VA_INIT,48);
    }

LOCAL_C VOID ConnectToWindowServer(VOID)
    {
    wConnect(f_alloc(sizeof(WSERV_SPEC)),0,W_CONNECT_AT_BACK);
    }

LOCAL_C VOID CaptureKeys(VOID)
    {
    wCaptureKey(KATS_RECORD,KATS_MODS,KATS_MODS);
    wCaptureKey(KATS_PLAYBACK,KATS_MODS,KATS_MODS);
    wCaptureKey(KATS_PLAYBACK2,KATS_MODS,KATS_MODS);
    wCaptureKey(KATS_SAVE,KATS_MODS,KATS_MODS);
    }

#pragma save,ENTER_CALL

LOCAL_C INT Initialise(VOID)
    {
    FindOlib();
    CreateGate();
    LoadDialogData();
    CreateMacroStorage();
    ConnectToWindowServer();
    CaptureKeys();
    Message("Keyboard macro support loaded");
    return(0);
    }

#pragma restore

GLDEF_C INT main(VOID)
    {
    INT ret;

    ret=p_enter1(Initialise);
    if (!ret)
        MainLoop();
    return(ret);
    }
